package daobeauty.interceptor.invoke.impl;

import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.SQLQuery;
import org.hibernate.SessionFactory;

import daobeauty.interceptor.invoke.AbstractCurrentSessionInvokeStrategy;
import daobeauty.interceptor.invoke.ArgumentMask;
import daobeauty.interceptor.invoke.fill.ArraySqlQueryFill;
import daobeauty.interceptor.invoke.fill.CollectionSqlQueryFill;
import daobeauty.interceptor.invoke.fill.EmptyFill;
import daobeauty.interceptor.invoke.fill.MapQueryFill;
import daobeauty.interceptor.invoke.fill.MapSqlQueryFill;
import daobeauty.interceptor.invoke.fill.ParameterFillStrategy;
import daobeauty.interceptor.invoke.fill.PlainSqlQueryFill;
import daobeauty.interceptor.returntype.executor.ExecutionStrategy;
import daobeauty.interceptor.returntype.executor.sqlquery.SqlQueryExecutor;
import daobeauty.util.MethodSignatureUtils;

public class FindByNativeQueryInvoke extends AbstractCurrentSessionInvokeStrategy {
	private static final Log LOG = LogFactory.getLog(FindByNativeQueryInvoke.class);
	
	private final ExecutionStrategy executor;
	private final ArgumentMask argumentMask;
	private final ParameterFillStrategy fillStrategy;
	
	@SuppressWarnings("unchecked")
	public FindByNativeQueryInvoke(Method method, SessionFactory sessionFactory, Class domainClass) {
		super(sessionFactory, domainClass);
		final Class [] parameterTypes = method.getParameterTypes();
		if (parameterTypes==null || parameterTypes.length<1 || !String.class.isAssignableFrom(parameterTypes[0])){
			throw new IllegalArgumentException("method "+MethodSignatureUtils.toMethodSignature(method)+" must have at least one string parameter as first: the sql query.");
		}
		executor = new SqlQueryExecutor(method.getReturnType());
		
		// case public List findAll(String hqlQuery);
		if (parameterTypes.length==1){
			fillStrategy = EmptyFill.INSTANCE;
			argumentMask = new ArgumentMask(method, 1);
		}
		// case public List findAll(String hqlQuery, Map valuesByName);
		else if (parameterTypes[1]!=null && Map.class.isAssignableFrom(parameterTypes[1])){
			fillStrategy = new MapSqlQueryFill();
			argumentMask = new ArgumentMask(method, 1);
		}
		// case public List findAll(String hqlQuery, Collection values);
		else if (parameterTypes[1]!=null && Collection.class.isAssignableFrom(parameterTypes[1])){
			fillStrategy = new CollectionSqlQueryFill();
			argumentMask = new ArgumentMask(method, 1);
		}
		// case public List findAll(String hqlQuery, Object [] values);
		else if (parameterTypes[1]!=null && Object[].class.isAssignableFrom(parameterTypes[1])){
			fillStrategy = new ArraySqlQueryFill();
			argumentMask = new ArgumentMask(method, 1);
		}
		// cases public List findAll(String hqlQuery, Object parameter);
		// cases public List findAll(String hqlQuery, Object parameter1, Object parameter2, ...);
		else{
			fillStrategy = new PlainSqlQueryFill();
			argumentMask = new ArgumentMask(method, 1);
		}
		if (LOG.isInfoEnabled()){
			LOG.info("created findByNativeQuery strategy with arguments for "+(fillStrategy instanceof MapQueryFill?"map of named ":fillStrategy.toString())+" parameters "+(argumentMask.hasLimiting()?", limiting":"")+(argumentMask.hasSorting()?", sorting":"")+" for method "+MethodSignatureUtils.toMethodSignature(method));
		}
	}

	public Object invoke(String methodName, Object[] args) throws Exception {
		final SQLQuery query = getCurrentSession().createSQLQuery((String)args[0]);
		LOG.info("query: "+query);
		fillStrategy.fillParameters(query, argumentMask.getFiltered(args));
		final Object o = executor.execute(query, argumentMask.getLimiting(args), argumentMask.getSorting(args));
		LOG.info("object type: "+o+", "+o.getClass());
		return o;
	}
}
